import Utils from "./utils.js";
class Line {
    _initWebgl() {
        const VSHADER_SOURCE = `
            attribute vec4 a_Position;
            attribute float a_PointSize;
            void main(){
            gl_Position = a_Position;
            gl_PointSize =a_PointSize;
            }`;

        const FSHADER_SOURCE = `
            precision mediump float;
            uniform vec4 u_FragColor;
            uniform bool pointFlag;
            void main(){
                if(pointFlag){
                    float dist =distance(gl_PointCoord,vec2(0.5,0.5));
                    if(dist<0.5){
                        gl_FragColor = u_FragColor;
                    }else{
                        discard;
                    }
                }else{
                    gl_FragColor = u_FragColor;
                }
            }`;

        if (!initShaders(this.gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
            console.error('Failed to initialize shaders');
            return;
        }
    }

    _pointsTranslationPosition(array, formerLineWidth, offsetIndex = 0) {
        let lineWidth = 0.5 * formerLineWidth;
        let ArrayData = [];
        let ArrayIndex = [];
        let newArray = [];
        let index = [];
        for (let i = 0, length = array.length; i < length - 2; i += 2) {
            if (newArray.length > 5000) {
                ArrayData = ArrayData.concat(newArray);
                newArray = [];
            }
            if (index.length > 5000) {
                ArrayIndex = ArrayIndex.concat(index);
                index = [];
            }

            let [x1, y1, x2, y2] = [array[i], array[i + 1], array[i + 2], array[i + 3]];
            newArray = newArray.concat(this._translationPosition(x1, y1, x2, y2, lineWidth));
            let temp = (ArrayData.length + newArray.length) / 2 + offsetIndex;
            index.push(temp - 4, temp - 3, temp - 2, temp - 3, temp - 2, temp - 1);
        }

        ArrayData = ArrayData.concat(newArray);
        ArrayIndex = ArrayIndex.concat(index);
        return {
            ArrayData: ArrayData,
            ArrayIndex: ArrayIndex
        };
    }

    _pointInPolgon(p, p1, p2, p3, p4) {
        let p1p2 = [p2[0] - p1[0], p2[1] - p1[1]];
        let p1p = [p[0] - p1[0], p[1] - p1[1]];
        let temp1 = p1p2[0] * p1p[0] + p1p[1] * p1p2[1];
        let p3p4 = [p4[0] - p3[0], p4[1] - p3[1]];
        let p3p = [p[0] - p3[0], p[1] - p3[1]];
        let temp2 = p3p[0] * p3p4[0] + p3p4[1] * p3p[1];
        if (temp1 * temp2 >= 0) {
            let p2p3 = [p3[0] - p2[0], p3[1] - p2[1]];
            let p2p = [p[0] - p2[0], p[1] - p2[1]];
            let temp3 = p2p3[0] * p2p[0] + p2p3[1] * p2p[1];
            let p4p1 = [p1[0] - p4[0], p1[1] - p4[1]];
            let p4p = [p[0] - p4[0], p[1] - p4[1]];
            let temp4 = p4p1[0] * p4p[0] + p4p1[1] * p4p[1];
            if (temp3 * temp4 >= 0) {
                return true;
            }
        }
        return false;
    }

    _pointsTranslationPositionBevel(array, formerLineWidth) {
        let lineWidth = 0.5 * formerLineWidth;
        let newArray = [];
        let newArray2 = [];
        let t = 0;
        for (let i = 0, length = array.length; i < length - 2; i += 2) {
            let [x1, y1, x2, y2] = [array[i], array[i + 1], array[i + 2], array[i + 3]];
            newArray = newArray.concat(_translationPosition(x1, y1, x2, y2, lineWidth));
            if (i > 0) {
                let length = newArray.length;
                let [ra1fx, raf1y, raf2x, raf2y] = [newArray[length - 16], newArray[length - 15], newArray[length - 14], newArray[length - 13]];
                let [ra1x, ra1y, ra2x, ra2y] = [newArray[length - 12], newArray[length - 11], newArray[length - 10], newArray[length - 9]];
                let [rb1x, rb1y, rb2x, rb2y] = [newArray[length - 8], newArray[length - 7], newArray[length - 6], newArray[length - 5]];
                let [cax1, cay1, cax2, cay2] = [newArray[length - 4], newArray[length - 3], newArray[length - 2], newArray[length - 1]];
                let now = new Date().getTime();

                if (!_pointInPolgon([ra1x, ra1y], [rb1x, rb1y], [rb2x, rb2y], [cax2, cay2], [cax1, cay1])) {
                    newArray2.push(ra1x, ra1y);
                } else {
                    newArray2.push(ra2x, ra2y);
                }
                newArray2.push(x1, y1);

                if (!_pointInPolgon([rb1x, rb1y], [ra1fx, raf1y], [raf2x, raf2y], [ra1x, ra1y], [ra2x, ra2y])) {
                    newArray2.push(rb1x, rb1y);
                } else {
                    newArray2.push(rb2x, rb2y);
                }
            }
        }
        return newArray.concat(newArray2);
    }

    _translationPosition(x1, y1, x2, y2, lineWidthHalf) {
        let vectorXab = x2 - x1;
        let vectorYab = y2 - y1;
        let temp = lineWidthHalf * vectorXab / Math.sqrt(vectorYab * vectorYab + vectorXab * vectorXab);

        let ya1 = y1 + temp;
        let xa1 = vectorXab === 0 ? x1 - lineWidthHalf : x1 - vectorYab * (ya1 - y1) / vectorXab;
        let ya2 = y1 - temp;
        let xa2 = vectorXab === 0 ? x1 + lineWidthHalf : x1 - vectorYab * (ya2 - y1) / vectorXab;

        let xb1 = xa1 + vectorXab;
        let xb2 = xa2 + vectorXab;
        let yb1 = ya1 + vectorYab;
        let yb2 = ya2 + vectorYab;
        return [xa1, ya1, xa2, ya2, xb1, yb1, xb2, yb2];
    };


    _dashArrayNormalData(solid, empty) {
        const canvas = document.getElementById('webgl');
        let data = this.data;
        let width = this.webgl.offsetWidth;
        solid = solid / width;
        empty = empty / width;
        const caculate = (x0, y0, x1, y1, solid, dasharray) => {
            let makedPoint = []
            let width = Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
            let division = Math.floor(width / dasharray);
            let multiple = width / dasharray;
            let vectorX = x1 - x0;
            let vectorY = y1 - y0;
            let vectorSolidX = (x1 - x0) * (1 / division) * (solid / dasharray);
            let vectorSolidY = (y1 - y0) * (1 / division) * (solid / dasharray);
            let multipleX = (x1 - x0) * (1 / division);
            let multipleY = (y1 - y0) * (1 / division);
            for (let i = 0; i < division - 1; i++) {
                makedPoint.push([x0 + multipleX * i, y0 + multipleY * i, (x0 + vectorSolidX) + multipleX * i, (y0 + vectorSolidY) + multipleY * i])
            }
            makedPoint.push([(x0) + multipleX * (division - 1), (y0) + multipleY * (division - 1), x1, y1]);
            return makedPoint;
        }

        let dasharray = solid + empty;
        let newPoints = [];
        let tempPoint = [];
        for (let i = 0, length = data.length; i < length; i++) {
            for (let j = 0, length1 = data[i].length; j < length1 - 2; j += 2) {
                if (tempPoint.length > 5000) {
                    newPoints = newPoints.concat(tempPoint);
                    tempPoint = [];
                }
                tempPoint = tempPoint.concat(caculate(data[i][j], data[i][j + 1], data[i][j + 2], data[i][j + 3], solid, dasharray));
            }
        }
        newPoints = newPoints.concat(tempPoint);
        tempPoint = [];
        return newPoints
    }


    _drawNormalLine(lineWidth, offsetIndex = 0) {
        let gl = this.gl;
        let data = this.data;
        let newData = [];
        let index = [];
        let lineNumber = data.length;
        gl.uniform4f(this.u_FragColor, ...[0.68, 0.45, 0.55, 1.0]);
        gl.uniform1f(this.pointFlag, false);
        for (let i = 0; i < lineNumber; i++) {
            let data2 = data[i];
            let obj = this._pointsTranslationPosition(data2, lineWidth, (offsetIndex + newData.length) / 2);
            newData = newData.concat(obj.ArrayData);
            index = index.concat(obj.ArrayIndex);
            if (newData.length > 1500 || index.length > 1500) {
                gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(newData), gl.DYNAMIC_DRAW);
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
                gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
                newData = [];
                index = [];
            }
        }
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(newData), gl.DYNAMIC_DRAW);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
        gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
        newData = [];
        index = [];
    }

    _lineWithoutWidthDash() {
        let data = [];
        let index = [];
        let num = 0;
        for (let i = 0, length = this.data.length; i < length; i++) {
            for (let j = 0; j < this.data[i].length; j++) {
                data.push(this.data[i][j])
            }
        }
        for (let i = 0, length = data.length / 2; i < length; i++) {
            index.push(num++)
        }

        let gl = this.gl;
        gl.uniform4f(this.u_FragColor, ...[0.68, 0.45, 0.55, 1.0]);
        gl.uniform1f(this.pointFlag, false);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.DYNAMIC_DRAW);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
        gl.drawElements(gl.LINES, index.length, gl.UNSIGNED_SHORT, 0);
    }

    _lineWithoutWidth() {
        let index = [];
        let data = [];
        let num = 0;
        for (let i = 0, length = this.data.length; i < length; i++) {
            for (let j = 0; j < this.data[i].length; j++) {
                data.push(this.data[i][j])
            }
        }

        for (let i = 0, length = this.data.length; i < length; i++) {
            for (let j = 0; j < this.data[i].length / 2; j++) {
                if (j == 0) {
                    index.push(num++)
                } else if (j == (this.data[i].length / 2) - 1) {
                    index.push(num++)
                } else {
                    let n = num++
                        index.push(n, n)
                }
            }
        }
        let gl = this.gl;
        gl.uniform4f(this.u_FragColor, ...[0.68, 0.45, 0.55, 1.0]);
        gl.uniform1f(this.pointFlag, false);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.DYNAMIC_DRAW);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
        gl.drawElements(gl.LINES, index.length, gl.UNSIGNED_SHORT, 0);
    }

    _lineJoin(lineJoin, lineWidth, canvasWidth) {
        let data = this.data;
        let gl = this.gl;
        switch (lineJoin) {
            case 'miter':
                {
                    let formData = [];
                    for (let i = 0, length = this.data.length; i < length; i++) {
                        for (let j = 0; j < this.data[i].length - 1; j++) {
                            formData.push([this.data[i][j], this.data[i][j + 1]])
                        }
                    }
                    this._drawMiterLineData(formData);
                }
            case 'bevel':
                {
                    this.lineWidth = lineWidth / this.webgl.width;
                    this._drawNormalLine(lineWidth / this.webgl.width, 0);
                    let joinData = [];
                    let joinDataIndex = [];
                    for (let i = 0, length = data.length; i < length; i++) {
                        let obj = this._pointsTranslationPosition(data[i], this.lineWidth);
                        let tempData = obj.ArrayData;
                        for (let j = 0; j < tempData.length - 8; j += 8) {
                            let temp = j / 2 + joinData.length / 2;
                            joinDataIndex.push(temp + 2, temp + 3, temp + 4, temp + 2, temp + 3, temp + 5);
                        }
                        joinData = joinData.concat(obj.ArrayData);
                        if (joinDataIndex.length > 5000) {
                            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(joinData), gl.DYNAMIC_DRAW);
                            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(joinDataIndex), gl.DYNAMIC_DRAW);
                            gl.uniform1f(this.pointFlag, false);
                            gl.drawElements(gl.TRIANGLES, joinDataIndex.length, gl.UNSIGNED_SHORT, 0);
                            joinData = [];
                            joinDataIndex = [];
                        }
                    }
                    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(joinData), gl.DYNAMIC_DRAW);
                    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(joinDataIndex), gl.DYNAMIC_DRAW);
                    gl.uniform1f(this.pointFlag, false);
                    gl.drawElements(gl.TRIANGLES, joinDataIndex.length, gl.UNSIGNED_SHORT, 0);
                    break
                }
            default:
                { 
                    this.lineWidth = lineWidth / this.webgl.width;
                    this._drawNormalLine(this.lineWidth, 0);
                    let point = [];
                    let num = 0;
                    let index = [];
                    for (let i = 0, length = data.length; i < length; i++) {
                        for (let j = 0; j < data[i].length - 2; j += 2) {
                            if (j != 0 && j != (data[i].length) - 2) {
                                point.push(data[i][j], data[i][j + 1]);
                            }
                        }
                    }
                    for (let i = 0; i < point.length; i++) {
                        index.push(num++);
                    }
                    gl.vertexAttrib1f(this.a_PointSize, this.lineWidth * (this.webgl.width) / 2);
                    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(point), gl.DYNAMIC_DRAW);
                    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
                    gl.uniform1f(this.pointFlag, true);
                    gl.drawElements(gl.POINTS, point.length / 2, gl.UNSIGNED_SHORT, 0);
                    break
                }
        }
    }

    _lineCap(lineCap, lineWidth, canvasWidth) {
        let data = this.data;
        let gl = this.gl;
        let point = [];
        let index = [];
        let num = 0;
        switch (lineCap) {
            case 'butt':
                {
                    break
                }
            case 'square':
                {
                    let point = [];
                    let num = 0;
                    let index = []
                    for (let i = 0, length = data.length; i < length; i++) {
                        point.push(data[i][0], data[i][1], data[i][data[i].length - 2], data[i][data[i].length - 1]);
                    }
                    for (let i = 0; i < point.length; i++) {
                        index.push(num++)
                    }
                    gl.vertexAttrib1f(this.a_PointSize, this.lineWidth * (this.webgl.width) / 2);
                    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(point), gl.DYNAMIC_DRAW);
                    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
                    gl.uniform1f(this.pointFlag, false);
                    gl.drawElements(gl.POINTS, point.length / 2, gl.UNSIGNED_SHORT, 0);
                    break
                }
            default:
                {
                    let point = [];
                    let num = 0;
                    let index = []
                    for (let i = 0, length = data.length; i < length; i++) {
                        point.push(data[i][0], data[i][1], data[i][data[i].length - 2], data[i][data[i].length - 1]);
                    }
                    for (let i = 0; i < point.length; i++) {
                        index.push(num++)
                    }
                    gl.vertexAttrib1f(this.a_PointSize, this.lineWidth * (this.webgl.width) / 2);
                    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(point), gl.DYNAMIC_DRAW);
                    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.DYNAMIC_DRAW);
                    gl.uniform1f(this.pointFlag, true);
                    gl.drawElements(gl.POINTS, point.length / 2, gl.UNSIGNED_SHORT, 0);
                    break
                }
        }
    }

    constructor(webgl) {
        this.webgl = webgl;
        this.gl = getWebGLContext(webgl);
        let gl = this.gl
        this._initWebgl();
        this.a_Position = gl.getAttribLocation(gl.program, 'a_Position');
        this.u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
        this.pointFlag = gl.getUniformLocation(gl.program, 'pointFlag');
        this.a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
        gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
        this.clearColor();
        gl.vertexAttribPointer(this.a_Position, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.a_Position);
    }

    clearColor(color = [0.0, 0.0, 0.0, 1.0]) {
        this.gl.clearColor(...color);
        this.gl.clear(this.gl.COLOR_BUFFER_BIT);
    }

    render(lineSize, pathNumber, pathPointNum, lineStyle, lineColor = [0.68, 0.45, 0.55, 1.0]) {
        const utils = new Utils();
        this.data = utils.initWebGLData(pathNumber, pathPointNum);
        this.solidLength = lineStyle.solidLength;
        this.emptyLength = lineStyle.emptyLength;
        if (lineSize == 1) {
            if (lineStyle.lineStyle == 'dashArray') {

                this.data = this._dashArrayNormalData(this.solidLength, this.emptyLength)
                this._lineWithoutWidthDash()
            } else {
                this._lineWithoutWidth()
            }
        } else {
            if (lineStyle.lineStyle == 'dashArray') {
                let offsetIndex;
                this.lineWidth = lineSize / this.webgl.width
                this.data = this._dashArrayNormalData(this.solidLength, this.emptyLength)
                this._drawNormalLine(this.lineWidth, offsetIndex = 0)

            } else {
                this._lineJoin(lineStyle.lineJoin, lineSize, this.webgl.width)
                this._lineCap(lineStyle.lineCap, lineSize, this.webgl.width)
            }
        }
    }
}

class LineByCanvas {
    constructor(canvas) {
        this.canvas = canvas;
        this.context = canvas.getContext('2d');
    }

    render(lineSize, pathNumber, pathPointNum, lineStyle) {
        this.canvas.width = this.canvas.width;
        this.canvas.height = this.canvas.height;
        let utils = new Utils();
        let data = utils.initCanvasData(pathNumber, pathPointNum);
        let context = this.context;
        context.lineWidth = lineSize / 2;
        context.strokeStyle = '#ff0000';
        context.lineJoin = lineStyle.lineJoin;
        context.lineCap = lineStyle.lineCap;
        if (lineStyle.lineStyle == 'dashArray') {
            context.setLineDash([lineStyle.solidLength/2, lineStyle.emptyLength / 2]);

        }
        for (let i = 0, length = data.length; i < length; i++) {
            context.beginPath();
            context.moveTo(data[i][0], data[i][1]);
            for (let j = 2; j < pathPointNum * 2; j += 2) {
                context.lineTo(data[i][j], data[i][j + 1]);
            }
            context.stroke();
        }
    }
}

export {
    LineByCanvas,
    Line
}